package com.leyou.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.bean.Goods;
import com.leyou.bean.SearchRequest;
import com.leyou.dto.*;
import com.leyou.exception.LyException;
import com.leyou.exception.domain.ExceptionEnum;
import com.leyou.item.client.ItemClient;
import com.leyou.repository.SearchRepository;
import com.leyou.utils.BeanHelper;
import com.leyou.utils.HighlightUtils;
import com.leyou.utils.JsonUtils;
import org.apache.commons.lang.StringUtils;

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Double.parseDouble;

@Service
public class SearchService {

    @Autowired
    private SearchRepository searchRepository;

    @Autowired
    private ElasticsearchTemplate template;
    @Autowired
    private ItemClient client;

    public Goods buildGoods(SpuDTO spuDTO) {
        //获得skus
        Long id = spuDTO.getId();

        List<SkuDTO> skus = client.findSKus(id);
        List<Map<String, Object>> skuList = new ArrayList<>();
        //遍历skuDTOS
        skus.forEach(sku -> {
            //创建一个索引库中需要的sku的map
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            //这里sku的标题，只需要放置除去spuName之外的部分
            skuMap.put("title", sku.getTitle().substring(spuDTO.getName().length()));
            skuMap.put("price", sku.getPrice());
            //只需要第一个逗号之前的部分
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            //把map加入到list中
            skuList.add(skuMap);
        });
        //得到spu下所有sku价格的集合
        Set<Long> prieceSet = skus.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());


        //提供一个封装动态过滤条件参数的对象
        Map<String, Object> specMap = new HashMap<>();
        //先得到当前map的key的所有值，根据分类和搜索查询规格参数
        List<SpecParamDTO> specParams = client.findSpecParam(null, spuDTO.getCid3(), true);

        SpuDetailDTO spuDetailDTO = client.findSpuDetail(spuDTO.getId());

        String genericSpecStr = spuDetailDTO.getGenericSpec();
        String specialSpecStr = spuDetailDTO.getSpecialSpec();
        //将通用规格参数字段str格式转成Map
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //将特有规格参数字段str格式转成Map
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {
        });
        //遍历规格参数的集合
        specParams.forEach(specParam -> {
            String key = specParam.getName();
            Object value = null;
            //判断当前规格参数是否是通用规格参数
            if (specParam.getGeneric()) {
                value = genericSpecMap.get(specParam.getId());
            } else {
                value = specialSpecMap.get(specParam.getId());
            }
            //判断value是否是数字类型
            if (specParam.getNumeric()) {
                //如果当前值是数字则直接兑换成区间存入到索引库中
                value = chooseSegment(value, specParam);
            }
            specMap.put(key, value);
        });
        //创建一个Goods对象
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setSpuName(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setAll(spuDTO.getName() + spuDTO.getCategoryName() + spuDTO.getBrandName());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setPrice(prieceSet);
        goods.setSpecs(specMap);
        return goods;
    }

    //算法，将数字转成区间的算法，参数一：数字，参数二：规格参数对象
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }


    /**
     * 搜索页面商品分页查询
     *
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> goodsPageQuery(SearchRequest request) {
        try {
            //创建一个封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装需要查询的字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "subTitle", "skus"}, null));
            //封装分页条件
            nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getSize()));
            //封装搜索条件
            nativeSearchQueryBuilder.withQuery(getQueryBuilder(request));
            HighlightUtils.highlightField(nativeSearchQueryBuilder, "spuName");
            //索引库查询
            AggregatedPage<Goods> goodsPage = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class, HighlightUtils.highlightBody(Goods.class, "spuName"));
            //封装一个自定义的分页对象
            PageResult<GoodsDTO> pageResult = new PageResult<>(BeanHelper.copyWithCollection(goodsPage.getContent(), GoodsDTO.class), (int) goodsPage.getTotalElements(),
                    goodsPage.getTotalPages()
            );
            return pageResult;
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
    }

    private QueryBuilder getQueryBuilder(SearchRequest request) {
        //创建一个组合过滤条件查询对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //封装条件查询 //.operator(Operator.AND)表示索引库中被搜索的字段，要具备搜索内容中全部词条，才展示。
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(), "all", "spuName").operator(Operator.AND));
        //获取到过滤条件的map
        Map<String, Object> filterParams = request.getFilterParams();
        //遍历过滤条件
        filterParams.entrySet().forEach(entry->{
            String key = entry.getKey();
            Object value = entry.getValue();
            //对key进行处理
            if(StringUtils.equals(key, "分类")){
                key = "categoryId";
            }else if(StringUtils.equals(key, "品牌")){
                key = "brandId";
            }else {
                key = "specs."+key+".keyword";
            }
            //封装过滤条件
            boolQuery.filter(QueryBuilders.termQuery(key, value));
        });
        return boolQuery;
    }

    public Map<String, List<?>> queryFilterParamData(SearchRequest request) {
        //创建出一个返回值对象
        Map<String, List<?>> result = new LinkedHashMap<>();
        //创建一个封装复杂条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页条件,类似于不差任何数据列表，只要聚合结果
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //封装搜索条件
        nativeSearchQueryBuilder.withQuery(getQueryBuilder(request));
        //给分类聚合结果起名称
        String categoryAgg = "categoryAgg";
        //添加分类的聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //给品牌聚合结果起名称
        String brandAgg = "brandAgg";
        //添加分类的聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        //聚合查询
        AggregatedPage<Goods> goodsGroup = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //得到所有的聚合结果
        Aggregations aggregations = goodsGroup.getAggregations();
        //根据分类名称获取到分类的聚合结果
        Terms categoryTerms = aggregations.get(categoryAgg);
        //将分类的聚合结果添加到结果map中
        List<Long> longList = handlerCategoryAgg(result, categoryTerms);
        //根据品牌名称获取到品牌的聚合结果
        Terms brandTerms = aggregations.get(brandAgg);
        //将品牌的聚合结果添加到结果map中
        handlerBrandAgg(result, brandTerms);
        handlerSpecParamData(longList, getQueryBuilder(request), result);
        return result;

    }

    private void handlerSpecParamData(List<Long> longList, QueryBuilder queryBuilder,  Map<String, List<?>> result) {
        //遍历分类id的集合
        longList.forEach(cid->{
            //创建一个封装复杂条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装分页条件,类似于不差任何数据列表，只要聚合结果
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
            //封装搜索条件
            nativeSearchQueryBuilder.withQuery(queryBuilder);
            //根据分类id获取规格参数
            List<SpecParamDTO> specParams = client.findSpecParam(null, cid, true);
            //遍历规格参数
            specParams.forEach(specParam -> {
                //给聚合结果起名称
                String aggName = specParam.getName();
                //过滤的字段域名字的样子为：specs.CPU频率.keyword
                String fieldName = "specs."+aggName+".keyword";
                //添加分类的聚合条件
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(fieldName));
            });
            //聚合查询
            AggregatedPage<Goods> goodsGroup = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
            //得到所有的聚合结果
            Aggregations aggregations = goodsGroup.getAggregations();
            //遍历规格参数
            specParams.forEach(specParam -> {
                //给聚合结果起名称
                String aggName = specParam.getName();
                //根据分类名称获取到分类的聚合结果
                Terms aggTerms = aggregations.get(aggName);
                //收集到terms中的String类型的key值
                List<String> params = aggTerms.getBuckets()
                        .stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                result.put(aggName, params);
            });

        });
    }

    private void handlerBrandAgg(Map<String, List<?>> result, Terms brandTerms) {
        List<BrandDto> brandDtos = brandTerms.getBuckets()
                .stream().map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .map(client::findById)
                .collect(Collectors.toList());
        result.put("品牌", brandDtos);
    }

    private List<Long> handlerCategoryAgg(Map<String, List<?>> result, Terms categoryTerms) {
        Stream<Long> longStream = categoryTerms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue);
        List<Long> longList = longStream.collect(Collectors.toList());
        List<CategoryDto> categoriesByIds = client.findCategoriesByIds(longList);
        result.put("分类", categoriesByIds);
        return longList;
    }
}

